#!/usr/bin/env python2.7

# bender.py

import os, sys
#import re
#from datetime import datetime, timedelta
#from dateutil.parser import parse
import numpy as np
import matplotlib
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

#from mpl_toolkits.basemap import Basemap
#from matplotlib import mlab
#from netCDF4 import Dataset
from PySide.QtCore import *
from PySide.QtGui import *
from ui_bumper import *
from metlib.kits import Null
from metlib.kits import ma_out, isinteger
from metlib.data.boundary import Limiter

class Faller(object):
    def __init__(self, speed0, accel):
        self.speed0 = speed0
        self.accel  = accel
        self.speed  = self.speed0

    def fall(self, height):
        new_height = height * (1.0 - self.speed)
        self.speed += self.accel
        if self.speed > 1.0:
            self.speed = 1.0
        return new_height

    def reset(self):
        self.speed = self.speed0

    def __repr__(self):
        return "<Falling Object: speed0=%s, accel=%s, speed=%s>" % (self.speed0, self.accel, self.speed)

class Bumper(QWidget, Ui_BumperWidget):
    def __init__(self, parent=None):
        super(Bumper, self).__init__(parent)
        self.setupUi(self)
        self.fall_v0_Label.setNum(self.fall_v0_Slider.value())
        self.fall_a_Label.setNum(self.fall_a_Slider.value())
        self.intensity_thres_Label.setNum(self.intensity_thres_Slider.value())
        self.intensity_thres2_Label.setNum(self.intensity_thres2_Slider.value())
        self.hthres_upper_Label.setNum(self.hthres_upper_Slider.value())
        self.hthres_lower_Label.setNum(self.hthres_lower_Slider.value())
        self._fall_fign = 20
        self.fall_fig = Figure(figsize=(1.5,1.6), dpi=96)
        self.fall_ax  = self.fall_fig.add_axes((0.0, 0.0, 1.0, 1.0))
        self.fall_ax.set_xlim(-0.5, self._fall_fign)
        self.fall_ax.set_ylim(-0.05, 1.05)
        self.fall_canvas = FigureCanvas(self.fall_fig)
        self.fall_fig_Layout.addWidget(self.fall_canvas)
        self.fall_figts = np.arange(self._fall_fign)
        self.fall_reset()
        self.fall_figline = self.fall_ax.plot(self.fall_figts, self.fall_fighs, 'o-', lw=1.2, ms=2.0, color='0.6')[0]
        self.fall_canvas.draw()

        self.fall_v0_Slider.valueChanged.connect(self.update_fall_fig)
        self.fall_a_Slider.valueChanged.connect(self.update_fall_fig)

    def update_fall_fig(self, *args):
        self.fall_reset()
        self.fall_figline.set_data(self.fall_figts, self.fall_fighs)
        self.fall_canvas.draw()
    
    def fall_reset(self):
        self.fall_v = self.fall_v0

    def fall(self, height):
        new_height = height * (1.0 - self.fall_v)
        self.fall_v += self.fall_a
        if self.fall_v > 1.0:
            self.fall_v = 1.0
        return new_height

    def bump(self, peaks_list, heights, start_i, start_hmin, start_hmax):
        time_inds = []
        height_series = []
        intensity_series = []
        L = Limiter(0, None)
        LT = Limiter(0, len(peaks_list))

        #TODO: add start up stuff.
        ps = peaks_list[start_i]
        sorted_ps = [p for p in ps if p.depth < 0]
        sorted_ps.sort(key=lambda p: p.depth)
        find_good_p = False
        for p in sorted_ps:
            if start_hmin <= heights[p.center] <= start_hmax:
                height_series.append(heights[p.center])
                intensity_series.append(p.depth)
                time_inds.append(start_i)
                find_good_p = True
                break
        if not find_good_p:
            return time_inds, height_series, intensity_series
#        for i in range(start_i+1, len(self.peaks_list)):
        i = start_i + 1
        while i < len(peaks_list):
            prev_h = height_series[-1]
#            intensity_thres = ma_out(intensity_series[L[i-1-start_i-self.n_intensity:i-start_i]]).mean() * self.thres
            intensity_thres_value = ma_out(intensity_series[-1:-self.intensity_n-1:-1]).mean() * self.intensity_thres
            intensity_thres_value2 = ma_out(intensity_series[-1:-self.intensity_n-1:-1]).mean() * self.intensity_thres2
            if not np.isfinite(intensity_thres_value):
#                print "intensity_thres_value is not finite! Done"
                break
            
            # according to intensity_thres2:
            find_good_p_according_to_thres2 = False
            for ti in range(i, LT(i+self.window_len)):
                sorted_ps = []
                ps = peaks_list[ti]
                for p in ps:
                    if p.depth < 0:
                        sorted_ps.append((ti, p))
                sorted_ps.sort(key=lambda ip: ip[1].depth)
                find_good_p = False
                for ti, p in sorted_ps:
                    if p.depth > intensity_thres_value2:
                        break
                    h = heights[p.center]
                    if prev_h*self.hthres_lower <= h <= prev_h*self.hthres_upper:
                        # Find good p!
                        fall_h = self.fall(prev_h)
                        if h >= fall_h:
                            new_h = h
                            self.fall_reset()
                        else:
                            new_h = fall_h
                        height_series.append(new_h)
                        intensity_series.append(p.depth)
                        time_inds.append(ti)
                        i = ti + 1
                        find_good_p = True
                        find_good_p_according_to_thres2 = True
#                        print "Good Peak 2: " , ti, h, intensity_thres_value
                        break
                if find_good_p_according_to_thres2:
                    break
            if find_good_p_according_to_thres2:
                continue

            # according to intensity_thres1
            sorted_ps = []
            for ti in range(i, LT(i+self.window_len)):
                ps = peaks_list[ti]
                for p in ps:
                    if p.depth < 0:
                        sorted_ps.append((ti, p))
#            ps = self.peaks_list[i]
#            sorted_ps = [p for p in ps if p.depth < 0]
#            sorted_ps.sort(key=lambda p: p.depth)
            sorted_ps.sort(key=lambda ip: ip[1].depth)
#            time_inds.append(i)
            find_good_p = False
            for ti, p in sorted_ps:
                if p.depth > intensity_thres_value:
                    break
                h = heights[p.center]
                if prev_h*self.hthres_lower <= h <= prev_h*self.hthres_upper:
                    # Find good p!
                    fall_h = self.fall(prev_h)
                    if h >= fall_h:
                        new_h = h
                        self.fall_reset()
                    else:
                        new_h = fall_h
                    height_series.append(new_h)
                    intensity_series.append(p.depth)
                    time_inds.append(ti)
                    i = ti + 1
                    find_good_p = True
#                    print "Good Peak: " , ti, h, intensity_thres_value
                    break
            if not find_good_p:
                fall_h = self.fall(prev_h)
                height_series.append(fall_h)
                intensity_series.append(np.nan)
                time_inds.append(i)
                i += (self.window_len + 1) / 2
        
        while intensity_series and not np.isfinite(intensity_series[-1]):
            time_inds.pop()
            height_series.pop()
            intensity_series.pop()
        return time_inds, height_series, intensity_series

    #properties
    # faller properties
    @property
    def fall_v0(self):
        return float(self.fall_v0_Slider.value()) / 1000.0

    @fall_v0.setter
    def fall_v0(self, value):
        self.fall_v0_Slider.setValue(int(value*1000.0))

    @property
    def fall_a(self):
        return float(self.fall_a_Slider.value()) / 1000.0

    @fall_a.setter
    def fall_a(self, value):
        self.fall_a_Slider.setValue(int(value*1000.0))

    @property
    def fall_fighs(self):
        res = [1.0]
        for ti in range(1, self._fall_fign):
            res.append(self.fall(res[-1]))
        return res

    # bumper parameters
    @property
    def intensity_thres2(self):
        return float(self.intensity_thres2_Slider.value()) / 100.0

    @intensity_thres2.setter
    def intensity_thres2(self, value):
        self.intensity_thres2_Slider.setValue(int(value*100.0))

    @property
    def intensity_thres(self):
        return float(self.intensity_thres_Slider.value()) / 100.0

    @intensity_thres.setter
    def intensity_thres(self, value):
        self.intensity_thres_Slider.setValue(int(value*100.0))

    @property
    def intensity_n(self):
        return self.intensity_n_Box.value()

    @intensity_n.setter
    def intensity_n(self, value):
        self.intensity_n_Box.setValue(int(value))

    @property
    def window_len(self):
        return self.window_len_Box.value()

    @window_len.setter
    def window_len(self, value):
        self.window_len_Box.setValue(int(value))

    @property
    def hthres_upper(self):
        return 1.0 + float(self.hthres_upper_Slider.value()) / 100.0

    @hthres_upper.setter
    def hthres_upper(self, value):
        if isinteger(value):
            finalvalue = value
        else:
            finalvalue = int((value - 1.0)*100.0)
        self.hthres_upper_Slider.setValue(finalvalue)

    @property
    def hthres_lower(self):
        return 1.0 - float(self.hthres_lower_Slider.value()) / 100.0

    @hthres_lower.setter
    def hthres_lower(self, value):
        if isinteger(value):
            finalvalue = value
        else:
            finalvalue = int((1.0 - value)*100.0)
        self.hthres_lower_Slider.setValue(finalvalue)

    @property
    def index(self):
        res = self.bumpList.currentRow()
        if res == -1:
            res = self.bumpList.count() - 1
        return res


if __name__ == '__main__':
    pass
